package com.itclass.auth.repository;


import com.itclass.auth.authentication.token.supprt.UsernamePasswordLoginAuthenticationToken;
import com.itclass.auth.constant.RedisCacheKeyConstants;
import com.itclass.auth.pojo.dto.UserDetailsDTO;
import com.itclass.auth.util.JwtUtils;
import com.itclass.auth.util.StringUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;

import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.util.CollectionUtils;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;

@Slf4j
public class RedisSecurityContextRepository extends AbstractSecurityContextRepository {
    public RedisSecurityContextRepository(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    private RedisTemplate<String, Object> redisTemplate;


    @Override
    public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {


        SecurityContext context = SecurityContextHolder.createEmptyContext();
        String token = requestResponseHolder.getRequest().getHeader(RedisCacheKeyConstants.AUTHORIZATION);
        UsernamePasswordLoginAuthenticationToken authenticationToken = new UsernamePasswordLoginAuthenticationToken();

        if (StringUtils.isNotEmpty(token) && redisTemplate.hasKey(RedisCacheKeyConstants.LOGIN_TOKEN + token)) {

            try {
                Claims claims = JwtUtils.getClaims(token);
                if (!CollectionUtils.isEmpty(claims)) {
                    authenticationToken.setAuthenticated(true);
                    authenticationToken.setPrincipal(claims.get("userName"));
                    authenticationToken.setAuthoritiesRoles((List<String>) claims.get("roles"));
                    context.setAuthentication(authenticationToken);
                    // 延迟加载用户详细认证信息，减少服务器资源网络io开销
                    authenticationToken.setSupplier(new Supplier<UserDetailsDTO>() {
                        @Override
                        public UserDetailsDTO get() {
                            String userLoginKey = RedisCacheKeyConstants.LOGIN_TOKEN + token;
                            UserDetailsDTO userDetailsDTO = (UserDetailsDTO) redisTemplate.opsForValue().get(userLoginKey);
                            return userDetailsDTO;
                        }
                    });
                }
            } catch (ExpiredJwtException e) {
                log.warn("token 过期了");
            }
        }
        return context;
    }

    @Override
    public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
        /**
         * 为什么这样判断？
         * 当我们登录认证成功的时候 Spring Security 会调用一次  saveContext()
         * 在 SecurityContextPersistenceFilter 过滤器的时候 结束的时候 又调用了一次  saveContext()
         * 这就导致了 一个登录认证的请求 如果不加这个判断 就会创建 两个 token 。有一个 token 是没有用到。
         */

        context = SecurityContextHolder.getContext();
        if (context != null && context.getAuthentication() != null) {
            // 获取认证信息
            Authentication authentication = context.getAuthentication();
            Object details = authentication.getDetails();
            List<String> roles = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
            String jwtToken = JwtUtils.getJwtToken((UserDetailsDTO) details, roles);
            String userLoginKey = RedisCacheKeyConstants.LOGIN_TOKEN + jwtToken;
            redisTemplate.opsForValue().set(userLoginKey, details);
            redisTemplate.expire(userLoginKey, JwtUtils.time, TimeUnit.MINUTES);
            request.setAttribute(RedisCacheKeyConstants.AUTHORIZATION, jwtToken);
        }
    }

    @Override
    public boolean containsContext(HttpServletRequest request) {

        String token = request.getHeader(RedisCacheKeyConstants.AUTHORIZATION);

        return StringUtils.isNotEmpty(token) && redisTemplate.hasKey(token);
    }

    @Override
    public void removeContext(HttpServletRequest request, HttpServletResponse response) {
        String jwtToken = request.getHeader(RedisCacheKeyConstants.AUTHORIZATION);
        if (StringUtils.isNotEmpty(jwtToken)) {
            redisTemplate.delete(RedisCacheKeyConstants.LOGIN_TOKEN + jwtToken);
        }
    }
}
